/*
 * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/pulse_cnt.h"
#include "esp_log.h"
#include "esp_check.h"
#include "rotary_encoder.h"

#define PCNT_HIGH_LIMIT     11
#define PCNT_LOW_LIMIT      -11
#define PCNT_RATIO          4

typedef enum {
    ROTARY_ENCODER_NONE = 0,
    ROTARY_ENCODER_OVERFLOW,
    ROTARY_ENCODER_UNDERFLOW
} rotary_encoder_overflow_t;

typedef struct RotaryEncoder {
    pcnt_unit_handle_t unit_handle;
    pcnt_channel_handle_t channel;
    rotary_encoder_event_cb event_callback;
    void* user_context;
    int last_encoder_count;
    rotary_encoder_overflow_t overflow;
    struct RotaryEncoder* next;
} rotary_encoder_t;

static const char* TAG = "r_enc";
static rotary_encoder_t* s_encoder_head = NULL;
static TaskHandle_t s_encoder_task = NULL;
static SemaphoreHandle_t s_semaphore = NULL;

static void encoder_task_routine(void* arg);
static bool encoder_reach_watch_point(pcnt_unit_handle_t unit_handle, const pcnt_watch_event_data_t* event_data, void* user_context);

esp_err_t rotary_encoder_create(const rotary_encoder_config_t* config, rotary_encoder_handle_t* encoder_handle_ptr)
{
    ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "invalid argument");

    esp_err_t ret = ESP_OK;
    rotary_encoder_t* rotary_encoder = heap_caps_calloc(1, sizeof(rotary_encoder_t), MALLOC_CAP_DEFAULT);
    ESP_RETURN_ON_FALSE(rotary_encoder, ESP_ERR_NO_MEM, TAG, "no mem for rotary encoder driver");

    rotary_encoder->event_callback = config->event_callback;
    rotary_encoder->user_context = config->user_context;
    rotary_encoder->overflow = ROTARY_ENCODER_NONE;

    pcnt_unit_config_t unit_config = {
        .high_limit = (PCNT_HIGH_LIMIT + 1) * PCNT_RATIO,
        .low_limit = (PCNT_LOW_LIMIT - 1) * PCNT_RATIO,
    };
    ESP_GOTO_ON_ERROR(pcnt_new_unit(&unit_config, &rotary_encoder->unit_handle), err, TAG, "pcnt_new_unit failed");

    pcnt_glitch_filter_config_t encoder_glitch_filter = {
        .max_glitch_ns = 1000,
    };
    ESP_GOTO_ON_ERROR(pcnt_unit_set_glitch_filter(rotary_encoder->unit_handle, &encoder_glitch_filter), err, TAG, "pcnt_unit_set_glitch_filter failed");

    pcnt_chan_config_t encoder_channel_config = {
        .edge_gpio_num = config->gpio_encoder_a,
        .level_gpio_num = config->gpio_encoder_b,
    };
    ESP_GOTO_ON_ERROR(pcnt_new_channel(rotary_encoder->unit_handle, &encoder_channel_config, &rotary_encoder->channel), err, TAG, "pcnt_new_channel failed");    
    ESP_GOTO_ON_ERROR(pcnt_unit_add_watch_point(rotary_encoder->unit_handle, 0), err, TAG, "pcnt_unit_add_watch_point failed");

    pcnt_event_callbacks_t encoder_event_callbacks = {
        .on_reach = encoder_reach_watch_point
    };
    ESP_GOTO_ON_ERROR(pcnt_unit_register_event_callbacks(rotary_encoder->unit_handle, &encoder_event_callbacks, rotary_encoder), err, TAG, "pcnt_unit_register_event_callbacks failed");

    ESP_GOTO_ON_ERROR(pcnt_channel_set_edge_action(rotary_encoder->channel, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE), err, TAG, "pcnt_channel_set_edge_action failed");
    ESP_GOTO_ON_ERROR(pcnt_channel_set_level_action(rotary_encoder->channel, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE), err, TAG, "pcnt_channel_set_level_action failed");
    ESP_GOTO_ON_ERROR(pcnt_unit_enable(rotary_encoder->unit_handle), err, TAG, "pcnt_unit_enable failed");
    ESP_GOTO_ON_ERROR(pcnt_unit_clear_count(rotary_encoder->unit_handle), err, TAG, "pcnt_unit_clear_count failed");
    ESP_GOTO_ON_ERROR(pcnt_unit_start(rotary_encoder->unit_handle), err, TAG, "pcnt_unit_start failed");

    bool is_first = NULL == s_encoder_head;

    if (NULL == s_semaphore)
    {
        s_semaphore = xSemaphoreCreateMutex();
    }

    if (pdTRUE == xSemaphoreTake(s_semaphore, portMAX_DELAY))
    {
        if (is_first)
        {
            s_encoder_head = rotary_encoder;

            if (NULL == s_encoder_task)
            {
                xTaskCreate(
                    encoder_task_routine,
                    "encoder_task",
                    2048,
                    NULL,
                    tskIDLE_PRIORITY,
                    &s_encoder_task
                );
            }
        }
        else
        {        
            for (rotary_encoder_t* current = s_encoder_head; true; current = current->next)
            {
                if (current->next != s_encoder_head)
                {
                    continue;
                }

                current->next = rotary_encoder;

                break;
            }
        }
        
        rotary_encoder->next = s_encoder_head;
        *encoder_handle_ptr = (rotary_encoder_handle_t)rotary_encoder;

        xSemaphoreGive(s_semaphore);
    }
    
    ESP_LOGI(
        TAG,
        "Rotary encoder config succeed. A: %d, B: %d",
        config->gpio_encoder_a,
        config->gpio_encoder_b
    );

    return ESP_OK;

err:
    if (rotary_encoder)
    {
        heap_caps_free((void*)rotary_encoder);
    }

    return ret;
}

esp_err_t rotary_encoder_remove(const rotary_encoder_handle_t encoder_handle)
{
    ESP_RETURN_ON_FALSE(encoder_handle, ESP_ERR_INVALID_ARG, TAG, "Pointer of handle is invalid");

    rotary_encoder_t* handle = (rotary_encoder_t*)encoder_handle;
    
    //ESP_RETURN_ON_ERROR(encoder_gpio_release(encoder_handle), TAG, "failed to release gpios");

    for (rotary_encoder_t* current = s_encoder_head; true; current = current->next)
    {
        if (current->next != handle)
        {
            continue;
        }

        current->next = handle->next;

        if (s_encoder_head == handle)
        {
            s_encoder_head = (handle == handle->next) ? NULL : handle->next;
        }

        break;
    }

    if (NULL == s_encoder_head)
    {
        //esp_timer_stop(s_encoder_timer_handle);
        //esp_timer_delete(s_encoder_timer_handle);
        //s_encoder_timer_handle = NULL;
    }

    heap_caps_free((void*)handle);

    return ESP_OK;
}

esp_err_t rotary_encoder_set_cb(const rotary_encoder_handle_t encoder_handle, rotary_encoder_event_cb event_cb, const void* user_context)
{
    ESP_RETURN_ON_FALSE(encoder_handle && event_cb, ESP_ERR_INVALID_ARG, TAG, "Pointer of handle is invalid");

    rotary_encoder_t* handle = (rotary_encoder_t*)encoder_handle;
    handle->event_callback = event_cb;
    handle->user_context = user_context;

    return ESP_OK;
}

static void encoder_task_routine(void* arg)
{
    ((void)arg);

    while(true)
    {
        if (pdTRUE == xSemaphoreTake(s_semaphore, portMAX_DELAY))
        {
            for (rotary_encoder_t* encoder_handle = s_encoder_head; true; encoder_handle = encoder_handle->next)
            {
                int encoder_raw_count;

                ESP_ERROR_CHECK(pcnt_unit_get_count(encoder_handle->unit_handle, &encoder_raw_count));

                const int encoder_count = encoder_raw_count / PCNT_RATIO;
                const int last_encoder_count = encoder_handle->last_encoder_count;
                rotary_encoder_event_t event = {
                    .direction = ROTARY_ENCODER_DIRECTION_NOT_SET,
                };

                if (encoder_count > encoder_handle->last_encoder_count)
                {
                    encoder_handle->last_encoder_count = encoder_count;

                    if (ROTARY_ENCODER_UNDERFLOW == encoder_handle->overflow)
                    {
                        event.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE;
                        encoder_handle->overflow = ROTARY_ENCODER_NONE;
                    }
                    else
                    {
                        event.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE;
                    }
                }
                else
                if (encoder_count < encoder_handle->last_encoder_count)
                {
                    encoder_handle->last_encoder_count = encoder_count;

                    if (ROTARY_ENCODER_OVERFLOW == encoder_handle->overflow)
                    {
                        event.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE;
                        encoder_handle->overflow = ROTARY_ENCODER_NONE;
                    }
                    else
                    {
                        event.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE;
                    }
                }

                if ((NULL != encoder_handle->event_callback) && (ROTARY_ENCODER_DIRECTION_NOT_SET != event.direction))
                {
                    encoder_handle->event_callback(&event, encoder_handle->user_context);
                }

                if (encoder_handle->next == s_encoder_head)
                {
                    break;
                }
            }
            
            xSemaphoreGive(s_semaphore);
        }

        vTaskDelay(pdMS_TO_TICKS(32));
    }
}

static bool encoder_reach_watch_point(pcnt_unit_handle_t unit_handle, const pcnt_watch_event_data_t* event_data, void* user_context)
{
    rotary_encoder_t* rotary_encoder = (rotary_encoder_t*)user_context;

    // r_enc: last_encoder: 0, watch_point: 0, zero_cross: 0        2,1 -> 0 decrement
    // r_enc: last_encoder: 0, watch_point: 0, zero_cross: 1        -2,-1 -> 0 increment
    // r_enc: last_encoder: -11, watch_point: 0, zero_cross: 1      -10,-11 -> 0 underflow
    // r_enc: last_encoder: 11, watch_point: 0, zero_cross: 0       10, 11 -> 0  overflow

    if ((PCNT_UNIT_ZERO_CROSS_POS_ZERO == event_data->zero_cross_mode) && (0 < rotary_encoder->last_encoder_count))
    {
        rotary_encoder->overflow = ROTARY_ENCODER_OVERFLOW;
    }
    else
    if ((PCNT_UNIT_ZERO_CROSS_NEG_ZERO == event_data->zero_cross_mode) && (0 > rotary_encoder->last_encoder_count))
    {
        rotary_encoder->overflow = ROTARY_ENCODER_UNDERFLOW;
    }

    return true;
}